import 'dart:math';

import 'package:flutter/material.dart';
import 'package:kq_flutter_core_widget/utils/ex/kq_ex.dart';
import 'package:kq_flutter_core_widget/widgets/chart/axis/bar/bar_entity.dart';
import 'package:kq_flutter_core_widget/widgets/chart/ex/extension.dart';
import 'package:kq_flutter_core_widget/widgets/chart/axis/x_axis.dart';
import 'package:kq_flutter_core_widget/widgets/chart/axis/y_axis.dart';
import 'package:kq_flutter_pad_widgets/widgets/chart/pad_chart/base/pad_base_chart.dart';

import '../base/pad_base_axis_chart.dart';
import '../pad_x_axis_render.dart';
import '../pad_y_axis_render.dart';

/// 柱状图代理
///
/// Created by wanggaowan on 2023/12/1 13:21
class KqPadBarChartDelegate extends PadBaseAxisChartDelegate<BarData> {
  /// 构建柱状图
  ///
  /// [xAxis]配置x轴相关属性，[xAxisRender]则负责x轴的绘制。
  /// [yAxis]配置y轴相关属性，[yAxisRender]则负责y轴的绘制。
  /// [dataRender]负责将柱状图数据绘制到界面及高亮数据绘制。
  /// [gestureHandler]为手势处理器，处理图表的点击，滑动等操作。
  /// [emptyWidgetBuilder]为无数据时构建需要显示的内容。
  /// [isEmptyData]用于判断给得的数据是否为空，从而决定是否调用[emptyWidgetBuilder]绘制空数据界面。
  /// [animDuration]为动画时间，此值大于0则绘制时伴随动画
  KqPadBarChartDelegate(
      {PadBarChartDataRender? dataRender,
      XAxis? xAxis,
      YAxis? yAxis,
      PadXAxisRender? xAxisRender,
      PadYAxisRender? yAxisRender,
      super.animDuration,
      super.data,
      PadBarCharGestureHandler? gestureHandler,
      super.emptyWidgetBuilder,
      super.isDataEmpty})
      : super(
          dataRender: dataRender ?? const PadBarChartDataRender(),
          xAxis: xAxis ?? XAxis(),
          yAxis: yAxis ?? YAxis(),
          xAxisRender:
              xAxisRender ?? PadXAxisRender(isDrawLabelByGridLine: false),
          yAxisRender: yAxisRender ?? PadYAxisRender(),
          gestureHandler: gestureHandler,
        );

  /// 构建柱状图，对非必传的功能提供默认实现
  ///
  /// [xAxis]配置x轴相关属性，[xAxisRender]则负责x轴的绘制。
  /// [yAxis]配置y轴相关属性，[yAxisRender]则负责y轴的绘制。
  /// [dataRender]负责将柱状图数据绘制到界面及高亮数据绘制。
  /// [gestureHandler]为手势处理器，处理图表的点击，滑动等操作。
  /// [emptyWidgetBuilder]为无数据时构建需要显示的内容。
  /// [isEmptyData]用于判断给得的数据是否为空，从而决定是否调用[emptyWidgetBuilder]绘制空数据界面。
  /// [animDuration]为动画时间，此值大于0则绘制时伴随动画
  KqPadBarChartDelegate.withDefault(
      {XAxis? xAxis,
      YAxis? yAxis,
      PadBarChartDataRender? dataRender,
      PadXAxisRender? xAxisRender,
      PadYAxisRender? yAxisRender,
      Duration? animDuration,
      BarData? data,
      PadBarCharGestureHandler? gestureHandler,
      Widget Function()? emptyWidgetBuilder,
      bool Function(BarData? data)? isDataEmpty})
      : this(
            xAxis: xAxis,
            yAxis: yAxis,
            dataRender: dataRender,
            xAxisRender: xAxisRender,
            yAxisRender: yAxisRender,
            animDuration: animDuration,
            data: data,
            gestureHandler: gestureHandler ?? PadBarCharGestureHandler(),
            emptyWidgetBuilder: emptyWidgetBuilder ?? getDefEmptyView,
            isDataEmpty: isDataEmpty);

  @override
  PadBarCharGestureHandler? get gestureHandler =>
      super.gestureHandler as PadBarCharGestureHandler?;

  @override
  XAxis get xAxis => super.xAxis as XAxis;

  @override
  YAxis get yAxis => super.yAxis as YAxis;

  @override
  bool get isEmptyData {
    var isDataEmptyFunc = isDataEmpty;
    if (isDataEmptyFunc != null) {
      return isDataEmptyFunc.call(data);
    }

    return data == null;
  }

  @override
  double calculateYAxisSize(covariant PadBaseChartDelegate delegate) {
    super.calculateYAxisSize(delegate);
    KqPadBarChartDelegate oldDelegate = delegate as KqPadBarChartDelegate;
    if (yAxis.max != oldDelegate.yAxis.max || oldDelegate.ySize.width == 0) {
      double maxWidth = 0;
      double value = yAxis.min;
      for (var i = 1; i < yAxis.labelCount - 1; i++) {
        value = value + yAxis.step;
        final textStyle = TextStyle(
          fontSize: yAxis.labelTextSize,
          overflow: TextOverflow.ellipsis,
        );
        final textPainter = TextPainter(
            text: TextSpan(text: value.toString(), style: textStyle),
            textDirection: TextDirection.ltr,
            maxLines: yAxis.labelMaxLines);
        textPainter.layout(); // 计算文本的布局
        if (maxWidth < textPainter.width) {
          maxWidth = textPainter.width;
        }
      }
      if (yAxis.labelMaxWidth != null && maxWidth > yAxis.labelMaxWidth!) {
        maxWidth = yAxis.labelMaxWidth!;
      } else if (maxWidth < yAxis.labelMinWidth) {
        maxWidth = yAxis.labelMinWidth;
      }
      maxWidth = maxWidth + yAxis.labelOffsetAxis + yAxis.lineWidth;

      return maxWidth;
    }
    return oldDelegate.ySize.width;
  }

  @override
  double getGridWidth(int labelCount, double xAxisLength) {
    return xAxisLength / xAxis.labelCount;
  }
}

/// 柱状图手势处理器
class PadBarCharGestureHandler extends PadBaseAxisChartGestureHandler<
    KqPadBarChartDelegate, List<BarEntity>> {
  PadBarCharGestureHandler(
      {super.tapEnable, super.dragEnable, super.onTap, super.touchData});

  @override
  List<BarEntity>? getTouchData(Offset offset, bool isMove) {
    var chart = super.chart;
    if (chart == null) {
      return null;
    }

    var data = chart.data;
    if (data == null || data.groups.isNullOrEmpty) {
      return null;
    }

    var list = <BarEntity>[];
    for (var entity in data.groups!) {
      for (var value in entity.values) {
        if (_isTouchInRectF(offset, value.drawRect, isMove)) {
          list.add(value);
          break;
        }
      }
    }
    return list.isEmpty ? null : list;
  }

  /// 点击是否在指定范围内容，[isYAllValid]表示是否y轴全局域触摸有效，如果为true，那么则无需判断手指y轴坐标
  bool _isTouchInRectF(Offset offset, Rect rect, bool isYAllValid) {
    if (offset.dx >= rect.left && offset.dx <= rect.right) {
      if (isYAllValid) {
        return true;
      }

      return offset.dy >= rect.top && offset.dy <= rect.bottom;
    }

    return false;
  }
}

const Color _maskColor = Color(0x55000000);

class BarChartHighLightRender {
  const BarChartHighLightRender(
      {this.maskColor = _maskColor, this.labelBuilder, this.labelOffset = 2});

  /// 高亮时柱状图的遮罩颜色
  final Color maskColor;

  /// 构建高亮要绘制的文本内容
  final HighLightLabel? Function(BarEntity data)? labelBuilder;

  /// 标签与柱形图的间距
  final double labelOffset;

  /// 绘制高亮内容，此时[BarEntity.drawRect]已就绪
  ///
  /// [canvas] 画布
  ///
  /// [rect] 水平可绘制区域。
  ///
  /// rect.left:起始点坐标 = x轴起始点 + [XAxis.startPadding]。
  ///
  /// rect.top:可绘制区域顶部坐标 = y轴顶部 - [YAxis.endPadding]。
  ///
  /// rect.right:水平可绘制区域结束点坐标 = x轴结束点 - [XAxis.endPadding]。
  ///
  /// rect.bottom:可绘制区域底部坐标 = y轴底部 + [YAxis.startPadding]。
  void onDraw(KqPadBarChartDelegate chart, Canvas canvas, Rect rect,
      List<BarEntity> data) {
    var paint = Paint();
    paint.color = maskColor;
    paint.style = PaintingStyle.fill;
    var textPaint = TextPainter(textDirection: TextDirection.ltr);
    for (var value in data) {
      canvas.drawRect(value.drawRect, paint);
      var label = labelBuilder?.call(value);
      if (label != null) {
        _drawText(
            chart,
            canvas,
            rect,
            value,
            label.text,
            label.color,
            label.size,
            textPaint,
            false,
            labelOffset,
            value.isLimitedByBarWidth);
      }
    }
    textPaint.dispose();
  }
}

void _drawText(
    KqPadBarChartDelegate chart,
    Canvas canvas,
    Rect rect,
    BarEntity data,
    String text,
    Color color,
    double size,
    TextPainter paint,
    bool fixTop,
    double labelOffset,
    bool isLimitedByBarWidth) {
  if (text.isEmpty) {
    return;
  }

  var textStyle = TextStyle(fontSize: size, color: color);

  paint.text = TextSpan(text: text, style: textStyle);

  paint.textAlign = TextAlign.center;
  if (isLimitedByBarWidth) {
    paint.layout(maxWidth: data.drawRect.width, minWidth: data.drawRect.width);
    var lines = paint.computeLineMetrics().length;
    if (lines > 1) {
      paint.textAlign = TextAlign.start;
      paint.layout(
          maxWidth: data.drawRect.width, minWidth: data.drawRect.width);
    }
  } else {
    paint.maxLines = 1;
    paint.layout(maxWidth: double.infinity, minWidth: 0);
  }

  double drawY;
  if (fixTop) {
    drawY = rect.top - labelOffset;
  } else {
    drawY = data.drawRect.top - labelOffset;
  }
  drawY = max(drawY - paint.height, rect.top - chart.yAxis.endPadding);
  if (isLimitedByBarWidth) {
    paint.paint(canvas, Offset(data.drawRect.left, drawY));
  } else {
    double dx = data.drawRect.left +
        (data.drawRect.right - data.drawRect.left) / 2 -
        paint.size.width / 2;
    if (dx < 0) {
      dx = 0;
    }
    paint.paint(canvas, Offset(dx, drawY));
  }
}

/// 柱状图高亮时绘制的标签
class HighLightLabel {
  HighLightLabel({this.text = '', this.color = Colors.black, this.size = 12});

  String text;
  Color color;
  double size;
}

class PadBarChartDataRender with PadBaseDataRenderMixin<KqPadBarChartDelegate> {
  const PadBarChartDataRender({this.highLightRender});

  final BarChartHighLightRender? highLightRender;

  @override
  void onDraw(KqPadBarChartDelegate chart, Canvas canvas, Rect rect,
      double gridWidth, double gridHeight, double animProgress) {
    var data = chart.data;
    if (data == null) {
      return;
    }

    var paint = Paint();
    paint.style = PaintingStyle.fill;

    var groups = data.groups;
    if (groups != null && groups.isNotEmpty) {
      drawBar(chart, canvas, groups, rect, gridWidth, animProgress, paint);

      var textPaint = TextPainter(textDirection: TextDirection.ltr);
      for (var value in groups) {
        for (var value1 in value.values) {
          if (value1.drawLabel) {
            _drawText(
                chart,
                canvas,
                rect,
                value1,
                value1.label ?? '',
                value1.labelColor,
                value1.labelTextSize,
                textPaint,
                value1.labelFixTop,
                value1.labelOffset,
                value1.isLimitedByBarWidth);
          }
        }
      }
      textPaint.dispose();
    }

    if (animProgress >= 1) {
      drawAddedLine(chart, canvas, data, rect, animProgress);
      var touchData = chart.gestureHandler?.touchData;
      if (touchData != null && touchData.isNotEmpty) {
        highLightRender?.onDraw(chart, canvas, rect, touchData);
      }
    }
  }

  /// 绘制柱状图
  @protected
  void drawBar(
      KqPadBarChartDelegate chart,
      Canvas canvas,
      List<BarGroupEntity> dataList,
      Rect rect,
      double gridWidth,
      double animProgress,
      Paint paint) {
    var yAxis = chart.yAxis;
    var tempStart = rect.left;
    var range = rect.height;
    var maxValue = yAxis.max - yAxis.min;

    for (var data in dataList) {
      if (data.values.isEmpty) {
        continue;
      }

      double totalPercent = data.values.fold(
          0, (previousValue, element) => previousValue + element.widthPercent);

      var width = gridWidth - data.barOffset * (data.values.length - 1);
      double drawStart;
      if (totalPercent >= 1 || data.gravity == BarGravity.start) {
        drawStart = tempStart;
      } else if (data.gravity == BarGravity.end) {
        drawStart = tempStart + width * (1 - totalPercent);
      } else {
        drawStart = tempStart + width * (1 - totalPercent) / 2;
      }

      for (var entity in data.values) {
        var barWidth = entity.widthPercent * width;
        var alpha = entity.bgColor.alpha;
        if (alpha > 0) {
          var rect2 = Rect.fromLTRB(
              drawStart, rect.top, drawStart + barWidth, rect.bottom);
          paint.color = entity.bgColor;
          paint.shader = null;
          canvas.drawRect(rect2, paint);
        }

        var barHeight =
            (entity.value - yAxis.min) / maxValue * range * animProgress;
        entity.$drawRect = Rect.fromLTRB(drawStart, rect.bottom - barHeight,
            drawStart + barWidth, rect.bottom);
        if (entity.barColors.length > 1) {
          paint.shader = LinearGradient(
                  colors: entity.barColors,
                  begin: Alignment.bottomCenter,
                  end: Alignment.topCenter)
              .createShader(entity.drawRect);
        } else {
          paint.shader = null;
        }
        paint.color = entity.barColors[0];
        canvas.drawRect(entity.drawRect, paint);

        drawStart += barWidth + data.barOffset;
      }

      tempStart += gridWidth;
    }
  }

  /// 绘制附加线
  @protected
  void drawAddedLine(KqPadBarChartDelegate chart, Canvas canvas,
      BarData barData, Rect rect, double animProgress) {
    var addedLines = barData.addedLines;
    if (addedLines == null || addedLines.isEmpty) {
      return;
    }

    var tempPath = Path();
    var textPaint = TextPainter(textDirection: TextDirection.ltr);
    var paint = Paint()..style = PaintingStyle.stroke;

    var yAxis = chart.yAxis;
    var xAxis = chart.xAxis;
    canvas.save();
    var tempRect = Rect.fromLTRB(rect.left, rect.top - chart.yAxis.endPadding,
        rect.right + xAxis.endPadding, rect.bottom);
    canvas.clipRect(tempRect);

    for (var addedLine in addedLines) {
      var y = rect.bottom -
          ((addedLine.value - yAxis.min) /
              (yAxis.max - yAxis.min) *
              (rect.bottom - rect.top));
      var stopX =
          addedLine.useEndPadding ? rect.right : rect.right + xAxis.endPadding;
      var startX = addedLine.useStartPadding
          ? rect.left + xAxis.startPadding
          : rect.left;
      paint
        ..color = addedLine.lineColor
        ..strokeWidth = addedLine.lineWidth;
      tempPath.reset();
      tempPath.moveTo(startX, y);
      tempPath.lineTo(stopX * animProgress, y);
      if (addedLine.lineDash != null) {
        tempPath = tempPath.dashPath(addedLine.lineDash!);
      }
      canvas.drawPath(tempPath, paint);

      var label = addedLine.label;
      if (animProgress < 1 ||
          label == null ||
          label.isEmpty ||
          addedLine.labelTextSize <= 0) {
        continue;
      }

      textPaint.text = TextSpan(
          text: label,
          style: TextStyle(
              color: addedLine.labelTextColor,
              fontSize: addedLine.labelTextSize,
              overflow: addedLine.textOverflow));

      textPaint.ellipsis =
          xAxis.textOverflow == TextOverflow.ellipsis ? kEllipsis : null;
      textPaint.textAlign = addedLine.labelAlignment.x == 0
          ? TextAlign.center
          : addedLine.labelAlignment.x < 0
              ? TextAlign.left
              : TextAlign.right;

      var textMaxWidth = stopX - startX;
      textPaint.layout(minWidth: textMaxWidth, maxWidth: textMaxWidth);
      var textHeight = textPaint.height;
      double offsetX;
      if (addedLine.labelAlignment.x > 0) {
        offsetX = startX - textMaxWidth / 2 * (1 - addedLine.labelAlignment.x);
      } else if (addedLine.labelAlignment.x < 0) {
        offsetX = startX + textMaxWidth / 2 * (addedLine.labelAlignment.x + 1);
      } else {
        offsetX = startX;
      }

      double offsetY =
          y - textHeight / 2 + addedLine.labelAlignment.y * textHeight / 2;

      textPaint.paint(
        canvas,
        Offset(offsetX, offsetY),
      );
    }

    canvas.restore();
    textPaint.dispose();
  }
}
